#include "ET++.ph"
#ifdef __GNUG__
#pragma implementation
#endif

#include "StaticTView.h"

#include "Class.h"
#include "String.h"
#include "View.h"
#include "Window.h"
#include "PrintManager.h"
#include "Look.h"
#include "TextFormatter.h"
#include "TextPainter.h"
#include "TextStyles.h"
#include "CheapText.h"
#include "Invariant.h"
#include "Document.h"
#include "Math.h"
#include "TextMarks.h"
#include "ET_stdio.h"

const Rectangle gFitRect(cFit, cFit);

void StaticTextView::Dump()
{
	for (int i= 0; i < nLines; i++) {
		if (MarkAtLine(i) == 0)
			fprintf(stderr, "Mark is NULL at %d\n!!!", i);
		else
			fprintf(stderr, "%d. %d-%d\n", i, StartLine(i), EndLine(i));
	}
	fprintf(stderr, "nLines= %d\n", nLines);
}

void StaticTextView::ClassInvariant()
{
	if (GetAssertLevel() > 5)
		return;
	if (nLines > lines->Size())
		fprintf(stderr, "!!!StaticTextView Invariant: nLines > lines->Size()!!!\n");
	for (int i= 1; i < nLines; i++) {
		if (MarkAtLine(i) == 0) {
			fprintf(stderr, "!!!StaticTextView Invariant: Mark is NULL at %d!!!\n", i);
			Dump();
		}
		else if (StartLine(i) == EndLine(i)) {
			fprintf(stderr, "!!!StaticTextView Invariant: Empty line at %d!!!\n", i);
			Dump();
		}
		else if (StartLine(i) != EndLine(i-1)) {
			fprintf(stderr, "!!!StaticTextView Invariant: Discontinous lines %d %d %d!!!\n", i, StartLine(i),
																EndLine(i-1));
			Dump();
		}
	}
}

static TextPainter *sPainter;
static TextFormatter *sFormatter;
static CheapText *sText;

ONEXIT(StaticTextView)
{
	SafeDelete(sPainter);
	SafeDelete(sFormatter);
	SafeDelete(sText);
}

//---- static methods ---------------------------------------------------------

Metric StaticTextView::MeasureText(const char *txt, Font* fd, int minwid, int l)
{
	if (l <= 1) {
		if (minwid > 0 || txt == 0)
			return Metric(minwid, fd->Spacing(), fd->Ascender());
		return Metric(fd->Width(txt), fd->Spacing(), fd->Ascender());
	}
	return Metric(minwid, l*fd->Spacing(), fd->Ascender());
}

void StaticTextView::DrawBoxedText(const Rectangle &r, const char *txt,
				Font *fp, Ink *ink, bool wrp, VObjAlign al)
{
	Point p(r.origin);
	p.y+= fp->Ascender();

	if (!wrp) {
		Metric m(fp->Width((byte*)txt), fp->Spacing(), fp->Ascender());
		
		switch (al & eVObjH) {
		case eVObjHLeft:
			break;
		case eVObjHCenter:
			p.x+= (r.extent.x - m.extent.x)/2;
			break;
		case eVObjHRight:
			p.x+= r.extent.x - m.extent.x;
			break;
		}
		switch (al & eVObjV) {
		//case eVObjVBase:
		//    p.y+= m.Base() - Base();
		//    break;
		case eVObjVCenter:
			p.y+= (r.extent.y - m.extent.y)/2;
			break;
		case eVObjVBottom:
			p.y+= r.extent.y - m.extent.y;
			break;
		case eVObjVTop:
		default:
			break;
		}
		
		if (m.extent.x > r.extent.x || m.extent.y > r.extent.y) {
			GrState gs;
			gs.Use();
			GrClipFurther(r);
			GrShowString(fp, ink, p, (byte*)txt);
		} else
			GrShowString(fp, ink, p, (byte*)txt);
		return;
	}
	if (sPainter == 0) {
		sPainter= new TextPainter;
		sFormatter= new FoldingFormatter;
		sText= new CheapText;
	}
	sText->ReplaceWithStr((byte*)txt);
	sText->SetFont(fp);

	int lines= sFormatter->Format(sText, sPainter, r.Width(), 0, sText->Size());

	GrState gs;
	gs.Use();
	GrClipFurther(r);
	Ink *oldtextink= GrGetTextInk();
	GrSetTextInk(ink);
	register int lh;
	Rectangle lr(p.x, p.y-fp->Ascender(), p.x, sFormatter->LineAt(0)->Height());
	int bh= sFormatter->LineAt(0)->Base();
	for (int i= 0; i < lines; i++) {
		int start= sFormatter->LineAt(i)->Pos(), end= sFormatter->LineAt(i)->End();
		sPainter->Draw(sText, p, start, end, lr, r, FALSE);
		p.y+= (lh= sFormatter->LineAt(i)->Height())-bh;
		if (i < lines-1)
			p.y+= (bh= sFormatter->LineAt(i+1)->Base());
		lr.origin.y+= lh;
	}
	GrSetTextInk(oldtextink);
}

//----- StaticTextView Methods -------------------------------------------------

NewMetaImpl(StaticTextView, View, (TP(text), TP(pager), TP(painter),
	TP(formatter), TE(align), TB(wrap), TB(drawViewBorder), TB(horExtend),
		T(border), T(nLines), TP(lines), TP(marks)));

StaticTextView::StaticTextView(EvtHandler *eh, const Rectangle &r, Text *t,
	bool w, TextViewFlags f, const Point &b, TViewAlign ta, int id)
				: View(eh, r, id)
{
	Init(r, t, w, f, b, ta);
}

void StaticTextView::Init(const Rectangle &r, Text* t, bool w,
			TextViewFlags f, const Point &b, TViewAlign ta)
{
	lines= new ObjArray(32);
	marks= new MarkList(FALSE, lines);
	contentRect= r;
	text= t;
	align= ta;
	wrap= w;
	drawViewBorder= FALSE;
	border= b;

	cline= 0;
	cYPosition= 0;

	horExtend= (contentRect.extent.x == cFit);
	if (horExtend)
		wrap= FALSE;
		
	if (!wrap)
		formatter= new SimpleFormatter();
	else
		formatter= new FoldingFormatter();
	painter= new TextPainter();

	RepairAll();

	if ((f & eTextViewInvis) == eTextViewInvis)
		SetFlag(eTextViewInvis);
	if ((f & eTextViewReadOnly) == eTextViewReadOnly)
		SetFlag(eTextViewReadOnly);
	if ((f & eTextWantFindFocus) == eTextWantFindFocus)
		SetFlag(eTextWantFindFocus);
	if ((f & eTextViewNoBatch) == eTextViewNoBatch)
		SetFlag(eTextViewNoBatch);
	if ((f & eTextViewNoClip) == eTextViewNoClip)
		SetFlag(eTextViewNoClip);
}

StaticTextView::~StaticTextView()
{
	if (lines)
		lines->FreeAll();
	SafeDelete(marks);
	SafeDelete(formatter);
	SafeDelete(painter);
	SafeDelete(pager);
}

void StaticTextView::Draw(Rectangle r)
{
	register int i, lh, bh;
	int startAt= PointToLine(r.origin-GetInnerOrigin());
	Ink *oldtextink= 0;

	if (startAt >= nLines)
		return;

	GrState gs;
	gs.Use();
	if (!TestFlag(eTextViewNoClip))
		GrClipFurther(Rectangle(GetInnerOrigin(), GetInnerExtent()));
	Point p= LineToPoint(startAt,TRUE) + GetInnerOrigin();
	Rectangle lineRect(
		GetInnerOrigin().x, p.y-BaseHeight(startAt),
		GetInnerExtent().x, LineHeight(startAt)
	);

	if (!Enabled()) {
		oldtextink= GrGetTextInk();
		GrSetTextInk(gLook->DisableInk());
	}

	bh= BaseHeight(startAt);
	for (i= startAt; ; i++) {
		if (! r.Intersects(lineRect))
			break;
		DrawLine(p, i, lineRect, r);
		if (i >= nLines-1)
			break;
		p.y+= (lh= LineHeight(i))-bh;
		p.y+= (bh= BaseHeight(i+1));
		lineRect.origin.y+= lh;
	}
	if (drawViewBorder) {
		GrSetPenInk(ePatGrey50);
		GrSetPenSize(2);
		GrStrokeRect (Rectangle (contentRect.origin,contentRect.extent));
	}
	if (! Enabled())
		GrSetTextInk(oldtextink);
}

void StaticTextView::Repair(int from, int to, bool redraw)
{
	int prevBreak, nextBreak;
	int prevLine, nextLine;

	ResetFlag(eTextViewModified);
	prevBreak= text->FindLastBreak(from);
	if (prevBreak < 0)
		prevLine= -1;
	else
		prevLine= CharToLine(prevBreak);
	prevLine= Math::Min(nLines-1, prevLine+1);
	nextBreak= text->FindNextBreak(to);
	if (nextBreak >= text->Size())
		nextLine= nLines-1;
	else
		nextLine= CharToLine(nextBreak);
		
	int n= formatter->Format(
		text, painter, GetInnerExtent().x,
		prevBreak+1, nextBreak+1
	);
	int extentDiff= UpdateView(prevLine, nextLine, n, redraw);
	UpdateComposition(prevLine, nextLine, n);
	UpdateExtent(extentDiff);
	if (pager)
		pager->Repaginate(prevLine);
//    ClassInvariant();
}

Rectangle StaticTextView::NextPageBreak(int pn, const Rectangle &r)
{
	if (pager == 0)
		pager= new TextPager();
	return pager->NextPageBreak(pn, r, this);
}

int StaticTextView::UpdateView(int prev, int next, int n, bool redraw)
{
	register int i, j;
	int invalid, valid;

	int newHeight= 0, oldHeight= 0;
	for (i= 0; i < n; i++)
		newHeight+= formatter->LineAt(i)->Height();
	for (i= prev; i <= next; i++)
		oldHeight+= MarkAtLine(i)->Height();

	if (redraw) {
		invalid= prev;
		valid= cMaxInt;
	} else {
		invalid= prev;
		for (i= prev; i < prev+n && i <= next; i++) {
			if (formatter->LineAt(i-prev)->IsDifferent(MarkAtLine(i)))
				break;
		}
		invalid= i;
				
		valid= nLines;
		if (oldHeight == newHeight) {
			for (j= prev+n-1; j > invalid && j < nLines; j--) {
				if (!formatter->LineAt(j-prev)->IsDifferent(MarkAtLine(j)))
					valid= j;
				else
					break;
			}
			if (valid == nLines)
				valid= Math::Min(nLines, prev+n);
			InvalidateRange(invalid, Math::Max(0, valid-1)); //
		}
		else
			InvalidateRange(invalid, Math::Max(0, valid-1));
	}
	InvalidateRange(invalid, Math::Max(0, valid-1));
	//---- update line cache
	LineToPoint(Math::Max(0, invalid-1));
	return newHeight-oldHeight;
}

void StaticTextView::UpdateComposition(int prev, int next, int n)
{
	int diff= n-(next-prev+1);
	register int i;
	LineMark *lm;

	if (lines->Size() < nLines+diff)
		lines->Expand((nLines+diff)*2);
	if (diff > 0) {
		for (i= nLines-1; i > next; i--)
			lines->AtPut(i+diff, lines->At(i));
		for (i= next+1; (i <= next+diff)  && (i < nLines) ; i++)
			lines->AtPut(i, 0);
	} else if (diff < 0) {
		for (i= next+diff+1; i <= next; i++) {
			lm= MarkAtLine(i);
			SafeDelete(lm);
		}
		for (i= next+1; i < nLines; i++)
			lines->AtPut(i+diff, MarkAtLine(i));
		for (i= nLines+diff; i < nLines; i++)
			lines->AtPut(i, 0);
	}

	for (i= prev; i < prev+n; i++) {
		LineMark *m1= MarkAtLine(i);
		if (m1 == 0)
			m1= new LineMark();
		m1->Copy(formatter->LineAt(i-prev));
		lines->AtPut(i, m1);
	}
	nLines+= diff;
}

void StaticTextView::DrawLine(const Point &p, int i, const Rectangle &lr,
													const Rectangle &cr)
{
	painter->Draw(text, p, StartLine(i), EndLine(i), lr, cr,
												TestFlag(eTextViewInvis));
}

void StaticTextView::SetFont(Font *fd)
{
	text->SetFont(fd);
	RepairAll();
}

void StaticTextView::SetFormatter(TextFormatter *f)
{
	SafeDelete(formatter);
	formatter= f;
}

void StaticTextView::SetPainter(TextPainter *p)
{
	SafeDelete(painter);
	painter= p;
}

void StaticTextView::SetPager(TextPager *p)
{
	SafeDelete(pager);
	pager= p;
}

void StaticTextView::SetAlign(TViewAlign m)
{
	align= m;
	RepairAll();
}

void StaticTextView::SetBorder(Point newBorder, bool repair)
{
	border= newBorder;
	if (repair)
		RepairAll();
}

void StaticTextView::ShowInvisibles(bool m)
{
	if (TestFlag(eTextViewInvis) != m) {
		SetFlag(eTextViewInvis, m);
		ForceRedraw();
	}
}

bool StaticTextView::GetShowInvis()
{
	return TestFlag(eTextViewInvis);
}

bool StaticTextView::IsReadOnly()
{
	return TRUE;
}

void StaticTextView::SetWordWrap(bool m)
{
	if (wrap == m)
		return;
	wrap= m;
	SafeDelete(formatter);
	if (wrap)
		formatter= new FoldingFormatter();
	else
		formatter= new SimpleFormatter();
	RepairAll();
}

bool StaticTextView::GetWordWrap()
{
	return wrap;
}

void StaticTextView::SetNoBatch(bool m)
{
	SetFlag(eTextViewNoBatch, m);
}

bool StaticTextView::GetNoBatch()
{
	return TestFlag(eTextViewNoBatch);
}

Text *StaticTextView::SetText(Text *t, bool scroll)
{
	Text *old= text;
	text= t;
	SetFlag(eTextViewModified);
	RepairAll();
	if (scroll == cRevealTop)
		Scroll(cPartScrollAbs, gPoint0, FALSE);
	Send(GetId(), cPartReplacedText, 0);
	return old;
}

void StaticTextView::SetString(byte *str, int len)
{
	text->ReplaceWithStr(str, len);
	RepairAll();
	Send(GetId(), cPartReplacedText, 0);
}

void StaticTextView::RepairAll()
{
	// flush line cache
	LineToPoint(0);
	nLines= 1;
	contentRect.extent.y= 2*border.y;
	LineMark *m= MarkAtLine(0);
	if (m == 0) {
		m= new LineMark();
		lines->AtPut(0, m);
	}
	m->ChangeMark(0, text->Size(), LineDesc(), eStateChanged);
	Repair(0, text->Size(), TRUE);
	ForceRedraw();
}

void StaticTextView::SetExtent(Point p)
{
	if (p != contentRect.extent) {
		View::SetExtent(p);
		horExtend= (p.x == cFit);
		if (horExtend)
			wrap= FALSE;
		SafeDelete(formatter);
		if (!wrap)
			formatter= new SimpleFormatter();
		else
			formatter= new FoldingFormatter();
		RepairAll();
	}
}

Metric StaticTextView::GetMinSize()
{
	if (TestFlag(eTextViewModified))
		RepairAll();
	return Metric(GetExtent(), LineToPoint(0, TRUE).y+border.y);
}

void StaticTextView::UpdateExtent(int dy)
{
	Point newExtent(contentRect.extent);
	Point newOrigin(contentRect.origin);
	register int i, s, e;

	newExtent.y+= dy;

	if (horExtend) {
		newExtent.x= 0;
		for (i= 0; i < nLines; i++) {
			s= StartLine(i);
			e= EndLine(i);
			newExtent.x= Math::Max(newExtent.x, painter->LineWidth(text, s, e));
		}
		newExtent.x += 2*border.x;
		if (align != eTViewLeft)
			newOrigin.x= AlignView(newExtent.x);
	}

	InvalidateDiff(newOrigin, newExtent);

	if (newOrigin != contentRect.origin)
		View::SetOrigin(newOrigin);
	if (newExtent != contentRect.extent)
		View::SetExtent(newExtent);
}

void StaticTextView::InvalidateDiff(const Point &newOrigin, const Point &newExtent)
{
	if (newExtent != contentRect.extent) {
		Rectangle r[8], oldRect(contentRect);
		oldRect.Expand(Point(4, 0));
		Rectangle newRect(newOrigin, newExtent);
		newRect.Expand(Point(4, 0));
		int n= Rectangle::Diff(r, newRect, oldRect);
		for (int i= 0; i < n; i++)
			InvalidateRect(r[i]);
	}
}

Point StaticTextView::LineToPoint(int n, bool basePoint, bool relative)
{
	register i, y= cYPosition;

//  int _cYPos, _cline;
//  _cYPos= cYPosition; _cline= cline;
	n= Math::Range(0, nLines, n);
	if (cline < n)
		for (i= cline; i < n; i++)
			y+= LineHeight(i);
	else if (cline > n)
		for (i= cline-1; i >= n; i--)
			y-= LineHeight(i);
	cYPosition= y;
	cline= n;

//    int yy= 0;
//    for (int ii= 0; ii < n; ii++)
//        yy+= LineHeight(ii);
//    if (cYPosition != yy)
//        fprintf(stderr, "LineToPoint: cYPosition %d!= %d, p=%d l=%d\n",
//            cline, n, _cYPos, _cline);

	if (basePoint)
		y+= BaseHeight(n);
	if (relative)
		return Point(0, y);
	return GetInnerOrigin() + Point(0, y);
}

int StaticTextView::PointToLine(const Point &p) // p is in coordinates relative to contentRect
{
	int i, py= Math::Range(0, contentRect.Height(), p.y), lh;

//    int _py, _cYPos, _cline;
//    _py= py; _cYPos= cYPosition; _cline= cline;
	if (py >= cYPosition) {
		for (i= cline; i < nLines; i++) {
			lh= LineHeight(i);
			if (cYPosition + lh > py)
				break;
			cYPosition+= lh;
		}
		cline= i;
	} else {
		for (i= cline-1; i >= 0 ; i--) {
			cYPosition-= LineHeight(i);
			if (cYPosition <= py)
				break;
		}
		cline= Math::Max(i, 0);
	}
		
//    register int ll, y2= 0;
//    for (ll= 0; ll < nLines; ll++) {
//        lh= LineHeight(ll);
//        if (y2 + lh > py)
//            break;
//        y2+= lh;
//    }
//    if (ll != cline)
//        fprintf(stderr, "PointToLine: cline %d!= %d, p=%d y=%d l=%d\n",
//            cline, ll, _py, _cYPos, _cline);
	return cline;
}

//---- map a point in view coordinates to line and character number

void StaticTextView::PointToPoint(const Point &pp, Point *viewPos, int *lineNo,
						int *charNo, bool relative)
{
	Point p(pp);
	if (!relative)
		p-= GetInnerOrigin();
	int l= PointToLine(p);
	if (l >= nLines) {
		l= nLines-1;
		p.x= contentRect.extent.x;    // set to end of line
	}
	if (l < 0) {
		l= 0;
		p.x= contentRect.origin.x;            // set to start of line
	}

	int cx= 0;
	int end= EndLine(l);
	int cn= painter->Map(text, StartLine(l), end, end, p.x,
							GetInnerExtent().x, &cx);

	if (charNo)
		*charNo= cn;
	if (lineNo)
		*lineNo= l;
	if (viewPos) {
		*viewPos= Point(cx, LineToPoint(l).y);
		if (!relative)
			(*viewPos) += GetInnerOrigin();
	}
}

int StaticTextView::CharToLine(int ch)
{
	register int base, pos, last, s;

	base= pos= 0;
	last= nLines-1;

	while (last >= base) { // binary search
		pos= (base+last) / 2;
		if (EndLine(pos) > ch && (s= StartLine(pos)) <= ch)
			break;
		if (StartLine(pos) > ch)
			last= pos-1;
		else
			base= pos+1;
	}
	return Math::Max(0, Math::Min(pos, nLines-1));
}

int StaticTextView::CharToPoint(int charNo, int *lineNo, Point *viewPos, bool relative)
{
	int line, ch, start, end, x;
	Point p;

	ch= Math::Range(0, text->Size(), charNo);
	line= CharToLine(ch);
	p.y= LineToPoint(line).y;
	if (line >= nLines && line > 0) { // beyound end of text
		p.y-= LineHeight(line);
		line= Math::Max(0,nLines-1);
	}
	start= StartLine(line);
	end= EndLine(line);

	ch= painter->Map(text, start, end, charNo, cMaxInt, GetInnerExtent().x, &x);
	if (viewPos) {
		*viewPos= p;
		viewPos->x+= x;
		if (!relative)
			*viewPos += GetInnerOrigin();
	}
	if (lineNo)
		*lineNo= line;
	return ch;
}

int StaticTextView::LineHeight(int l)
{
	return MarkAtLine(Math::Range(0, nLines-1, l))->Height();
}

int StaticTextView::BaseHeight(int l)
{
	return MarkAtLine(Math::Range(0, nLines-1, l))->Base();
}

void StaticTextView::InvalidateRange(int from, int to)
{
	if (from > to)  // normalize range
		SwapRange(from, to);
	from= Math::Range(0, nLines-1, from);
	to=   Math::Range(0, nLines-1, to);
	Point p(LineToPoint(from)),
	      t(contentRect.extent.x, LineToPoint(to).y+LineHeight(to));
	Rectangle r(NormRect(p, t));

	r+= GetInnerOrigin();
	if (from == 0)  { // consider border
		r.origin.y -= border.y;
		r.extent.y += border.y;
	}
	if (to == nLines-1 || to == 0)
		r.extent.y += border.y;
	InvalidateRect(r.Expand(Point(Math::Max(4,border.x),0)));
}

int StaticTextView::AlignView(int newX)
{
	// adjust origin
	switch (align) {
	case eTViewCenter:
		return contentRect.origin.x + (contentRect.extent.x-newX)/2;
	case eTViewRight:
		return contentRect.origin.x + (contentRect.extent.x-newX);
	default:
		return 0;
	}
}

void StaticTextView::InvalidateRange(int from, const Point &frpt, int to,
				const Point &topt)
{
	Point fp(frpt), tp(topt);
	from= Math::Range(0, nLines-1, from);
	to=   Math::Range(0, nLines-1, to);

	if ((from == to && fp.x > tp.x) || from > to) { // normalize range
		SwapRange(from, to);
		Swap(fp, tp);
	}

	if (from == to) { // optimize invalidate on one line
		Rectangle r(LineToPoint(from)+Point(fp.x, 0),
				Point(tp.x-fp.x, LineHeight(from)));
		if (from == 0)  { // consider border
			r.origin.y -= border.y;
			r.extent.y += border.y;
		}
		if (to == nLines-1 || to == 0)
			r.extent.y += border.y;
		r.origin+= GetInnerOrigin();
		InvalidateRect(r.Expand(Point(Math::Max(4, border.x), 0)));
		return;
	}
	InvalidateRange(from, to);
}

const char *StaticTextView::AsString()
{
	return text->AsString();
}

OStream& StaticTextView::PrintOn(OStream&s)
{
	View::PrintOn(s);
	return s << text SP << wrap SP << (int)align SP
			<< horExtend SP << border SP << contentRect.extent;
}

IStream& StaticTextView::ReadFrom(IStream &s)
{
	Text *txt;
	bool wrp, hExtend;
	Point extent, itsBorder;
	TViewAlign al;

	SafeDelete(text);

	View::ReadFrom(s);
	s >> txt >> Bool(wrp) >> Enum(al) >> Bool(hExtend) >> itsBorder >> extent;
	Rectangle r(extent);
	if (hExtend)
		r.extent.x= cFit;

	Init(r, txt, wrp, eTextViewNone, itsBorder, al);
	return s;
}

void StaticTextView::AddMark(Mark *m)
{
	text->AddMark(m);
}

Mark *StaticTextView::RemoveMark(Mark *m)
{
	return text->RemoveMark(m);
}

Iterator *StaticTextView::GetMarkIter()
{
	return text->GetMarkIter();
}

MarkList *StaticTextView::GetMarkList()
{
	return text->GetMarkList();
}

void StaticTextView::InspectorId(char *buf, int sz)
{
	if (text)
		text->InspectorId(buf, sz);
	else
		View::InspectorId(buf, sz);
}

void StaticTextView::CollectParts(Collection *col)
{
	View::CollectParts(col);
	col->Add(text);
}

void StaticTextView::PrintAdorn(const Rectangle&, int gPageNo)
{
	Rectangle r(gPrintManager->GetViewRect());
	Font *f= new_Font(eFontTimes, 12, eFaceBold);
	r.origin.y-= 10;

	byte *p= (byte*) GetDocument()->GetBaseName();
	Point o(f->AdjustString(p, r.NW(), eAdjVBottom, eAdjHLeft));
	GrShowString(f, gInkBlack, o, p);

	p= (byte*) form("Page: %d", gPageNo);
	o= f->AdjustString(p, r.NE(), eAdjVBottom, eAdjHRight);
	GrShowString(f, gInkBlack, o, p);
}
		
